Skip to content

feat(desktop): personal or organization principals, chosen not typed - #30

Merged
pyramation merged 1 commit into
mainfrom
feat/principal-scope-picker
Aug 9, 2026
Merged

feat(desktop): personal or organization principals, chosen not typed#30
pyramation merged 1 commit into
mainfrom
feat/principal-scope-picker

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

A principal no longer has to belong to an organization, and the org it does belong to is picked rather than typed. orgId becomes optional the whole way down — CreatePrincipalOptions, CreatePrincipalRequest, the IPC validator, --org — and its absence means personal: a sub-identity owned by you, reaching wherever you do, which is the shape an unattended job of your own actually wants.

The personal mutation is sent by hand, and that is a codegen bug worth knowing about. The auth schema does expose it —

input CreatePrincipalInput { name: String, entityIds: [UUID], isReadOnly: Boolean, bypassStepUp: Boolean, useAdminOwner: Boolean }
createPrincipal(input: CreatePrincipalInput!): CreatePrincipalPayload  # result: UUID

— but the generated ORM has no mutation.createPrincipal: the principals table's CRUD create carries the same inflected name and shadowed the procedure, so the typed surface offers createOrgPrincipal alone, and client.principal.create sends the wrong (nested, ownerId+userId) input. So the personal path goes through the SDK's own auth.FetchAdapter with the flat input above, and everything else — step-up hold/replay, rethrow, the endpoint explanations — is unchanged around it.

const principalId = options.orgId
  ? (await client.mutation.createOrgPrincipal({ input: { name, orgId, ...flags } }, )).createOrgPrincipal?.result
  : await createPersonalPrincipal(endpoint, token, { name, ...flags });

The dialog gains a Scope select (Personal / Organization); the organization row only exists for the second, and it is a dropdown of ids, with a free-text field behind "Another organization…" for the first principal in an org.

Those ids come from knownOrgIds(principals, keys) — the orgs this account has demonstrably scoped something to already (a principal's entityIds, an org key's orgId). Not a membership query, because there isn't one to call: memberships live behind the admin surface, which a signed-in user's own token does not reach. Offering ids the account has used is honest; inventing a list would not be.

Testing

knownOrgIds (dedupes, gathers from both sources, empty for a fresh account), a personal principal creating with entityIds: [], and the CLI's principal command now failing on a missing account rather than a missing --org. Desktop typecheck/lint/test, pnpm build/lint/test at the root: green.

Link to Devin session: https://app.devin.ai/sessions/04636534e07048089ffb6b78142e12cd
Requested by: @pyramation

@pyramation pyramation self-assigned this Aug 9, 2026
@devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit a0d65a4 into main Aug 9, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant